package res.graph;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;

import javax.swing.JOptionPane;

public class Edge {
	private double weight;
	private double load = 0.0;
	private final Graph g;
	private final Vertex source;
	private final Vertex target;
	private final HashSet<Edge> successors = new LinkedHashSet<Edge>();
	private final HashSet<Edge> partners = new LinkedHashSet<Edge>();
	private final HashSet<Edge> competitors = new LinkedHashSet<Edge>();
	private final Object lock = new Object();
	
	/**
	 * Cria uma nova aresta composta pelos v&eacute;rtices informados, com a sem&acirc;ntica correspondente.
	 * 
	 * <p>
	 * Este construtor automaticamente insere os v&eacute;rtices corretamente nas listas de vizinhos de cada um.
	 * Essa inser&ccedil;&atilde;o sup&otilde;e que o grafo &eacute; direcionado. Entretanto, os grafos direcionados n&atilde;o
	 * s&atilde;o afetados, pois a verifica&ccedil;&atilde;o a ser feita deve buscar as arestas em ambas as dire&ccedil;&otilde;es.
	 * 
	 * <p>
	 * Note que n&atilde;o importa em que ordem os v&eacute;rtices sejam interpretados na aresta, adicionar duas arestas contendo
	 * o mesmo par de v&eacute;rtices em um grafo deve ser interpretado como duas arestas distintas, seja o grafo direcionado
	 * ou n&atilde;o. 
	 * 
	 * @param source v&eacute;rtice de partida da aresta.
	 * @param target v&eacute;rtica de chegada da aresta.
	 */
	public Edge(Graph g, Vertex source, Vertex target) {
		this.source = source;
		this.target = target;
		this.weight = 0.0d;
		this.g = g;
		
		source.outNeighbours.add(target);
		target.inNeighbours.add(source);
	}
	
	public Edge(Graph g, Vertex source, Vertex target, double weight) {
		this(g, source, target);
		this.weight = weight;
	}
	
	public void addPartner(Edge e) {
		if(this.getSource().getOrd() == e.getSource().getOrd() && this.getTarget().getOrd() == e.getTarget().getOrd())
			return;
		else if(e != null && e != this && !this.equals(e)
				&& this.g == e.g && this.g.containsEdge(e.getSource(), e.getTarget())) {
			for(Edge p : partners) {
				if(!p.equals(e))
					p.partners.add(e);
			}
			
			if(!competitors.contains(e))
				partners.add(e);
		}
	}
	
	public void addPartners(Collection<Edge> edges) {
		for(Edge e : edges) {
			if(this.g == e.g && this.g.containsEdge(e.getSource(), e.getTarget())) {
				for(Edge p : partners) {
					if(!p.equals(e))
						p.partners.add(e);
				}

				if(this.getSource().getOrd() == e.getSource().getOrd() && this.getTarget().getOrd() == e.getTarget().getOrd())
					return;
				partners.add(e);
			}
		}
	}
	
	public void removePartner(Edge e) {
		for(Edge p : partners)
			p.partners.remove(e);
		
		partners.remove(e);
	}
	
	public void removePartners(Collection<Edge> edges) {
		for(Edge p : partners)
			p.partners.removeAll(edges);
		
		partners.removeAll(edges);
	}
	
	public void addCompetitor(Edge e) {
		if(this.getSource().getOrd() == e.getSource().getOrd() && this.getTarget().getOrd() == e.getTarget().getOrd())
			return;
		else if(e != null && e != this && !this.equals(e)
				&& this.g == e.g && this.g.containsEdge(e.getSource(), e.getTarget())) {
			for(Edge p : partners) {
				if(!p.equals(e))
					p.competitors.add(e);
			}
			
			if(!partners.contains(e))
				competitors.add(e);
		}
	}
	
	public void addCompetitors(Collection<Edge> edges) {
		for(Edge e : edges) {
			if(e != null && e != this && !this.equals(e)
					&& this.g == e.g && this.g.containsEdge(e.getSource(), e.getTarget())) {
				for(Edge p : partners) {
					if(!p.equals(e))
						p.competitors.add(e);
				}

				if(this.getSource().getOrd() == e.getSource().getOrd() && this.getTarget().getOrd() == e.getTarget().getOrd())
					return;
				competitors.add(e);
			}
		}
	}
	
	public void removeCompetitor(Edge e) {
		for(Edge p : partners)
			p.competitors.remove(e);
		
		competitors.remove(e);
	}
	
	public void removeCompetitors(Collection<Edge> edges) {
		for(Edge p : partners)
			p.competitors.removeAll(edges);
		
		competitors.removeAll(edges);
	}
	
	public boolean addSuccessor(Edge e) {
		if(e != null && this.g.containsEdge(e.getSource(), e.getTarget()))
			return successors.add(e);
		else
			return false;
	}
	
	public void addSuccessor(Collection<Edge> edges) {
		if(edges != null) {
			for(Edge e : edges) {
				if(this.g.containsEdge(e.getSource(), e.getTarget()))
					successors.add(e);
			}
		}
	}
	
	public void removeSuccessor(Edge e) {
		successors.remove(e);
	}
	
	public void removeSuccessor(Collection<Edge> edges) {
		successors.removeAll(edges);
	}
	
	public Graph getGraph() {
		return g;
	}
	
	public Collection<Edge> getSuccessors() {
		return successors;
	}
	
	public Iterable<Edge> getPartners() {
		return partners;
	}
	
	public Iterable<Edge> getCompetitors() {
		return competitors;
	}
	
	public Vertex getTarget() {
		return target;
	}
	
	public Vertex getSource() {
		return source;
	}
	
	public double getWeight() {
		return weight;
	}
	
	public double getLoad() {
		return load;
	}
	
	/**
	 * Aumenta o valor de carga corrente associado &agrave; aresta.
	 * 
	 * <p>
	 * Caso o fator de incremento informado se ja inválido (não-positivo) ou gere um
	 * &quote;transbordamento&quote; da aresta, a operação é cancelada.
	 * 
	 * <p>
	 * Este m&eacute;todo &eacute; seguro para uso em embientes multitarefa. 
	 * 
	 * @param incr fator de incremento (valor a ser acrescido).
	 * @return <code>true</code> caso foi possível incrementar, <code>false</code> caso contrário.
	 */
	public boolean increaseLoad(double incr) {
		synchronized (lock) {
			if(incr <= 0.0 || load + incr > weight)
				return false;
			else {
				load += incr;
				return true;
			}
		}
	}
	
	/**
	 * Diminui o valor de carga corrente associado &agrave; aresta.
	 * 
	 * <p>
	 * Caso o fator de decremento informado se ja inválido (não-positivo) ou gere um
	 * &quote;transbordamento&quote; da aresta, a operação é cancelada.
	 * 
	 * <p>
	 * Este m&eacute;todo &eacute; seguro para uso em embientes multitarefa. 
	 * 
	 * @param decr fator de decremento (valor a ser decrescido).
	 * @return <code>true</code> caso foi possível decrementar, <code>false</code> caso contrário.
	 */
	public boolean decreaseLoad(double decr) {
		synchronized (lock) {
			if(decr <= 0.0 || load - decr < 0.0)
				return false;
			else {
				load -= decr;
				return true;
			}
		}
	}
	
	@Override
	public int hashCode() {
		return (source.hashCode() ^ target.hashCode());
	}
	
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Edge) {
			Edge e = (Edge) obj;
			
			return e.source.equals(this.source) && e.target.equals(this.target); 
		} else
			return false;
	}
}
